using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Practices.PageFlow;
using Microsoft.Practices.PageFlow.Configuration;
using System.Globalization;
using System.Web;
using System.Reflection;
using Microsoft.Practices.PageFlow.Services;
using WCSFContrib.PageFlow.Xml.Configuration;
using WCSFContrib.PageFlow.Xml.Services;

namespace WCSFContrib.PageFlow.Xml
{
    public class XmlPageFlowProvider : IPageFlowProvider, IDisposable
    {
        private IPageFlowFactory _pageFlowFactory;
        private IPageFlowInstanceStore _store;

        public XmlPageFlowProvider(IPageFlowFactory factory, IPageFlowInstanceStore store)
		{
			_pageFlowFactory = factory;
			_store = store;
		}

        public XmlPageFlowProvider(PageFlowInstanceStoreProviderSection pageFlowInstanceStoreProviderSection, PageFlowInstanceCorrelationTokenProviderSection pageFlowCorrelationTokenProviderSection)
		{
			if (pageFlowInstanceStoreProviderSection == null)
				throw new ArgumentNullException("pageFlowInstanceStoreProviderSection");

            if (pageFlowCorrelationTokenProviderSection == null)
                throw new ArgumentNullException("pageFlowCorrelationTokenProviderSection");

            IDictionary<string, NavigationGraph> navigationGraphs = new WebConfigStore().GetXmlPageFlowNavigationGraphs(); 

            _pageFlowFactory = new XmlPageFlowFactory(navigationGraphs, new NavigationService());

            Type tokenProviderType = Type.GetType(pageFlowCorrelationTokenProviderSection.ProviderType, true, true);
            IPageFlowCorrelationTokenProvider _tokenProvider =
                (IPageFlowCorrelationTokenProvider)Activator.CreateInstance(tokenProviderType);

            Type storeType = Type.GetType(pageFlowInstanceStoreProviderSection.ProviderType, true, true);
            _store =
                (IPageFlowInstanceStore)Activator.CreateInstance(storeType, BindingFlags.CreateInstance, null,
                                                                  new object[]
				                                                  	{
				                                                  		pageFlowInstanceStoreProviderSection.ConnectionStringName,
                                                                        _tokenProvider
				                                                  	},
                                                                  CultureInfo.CurrentCulture);
            PopulateDirectory(navigationGraphs);
         }

        private static void PopulateDirectory(IDictionary<string, NavigationGraph> navigationGraphs)
        {
            foreach (NavigationGraph navGraph in navigationGraphs.Values)
            {
                PageFlowDirectory.Catalog.Add(new XmlPageFlowDefinition(navGraph));
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                _pageFlowFactory.Dispose();
            }
        }		

        public IPageFlow GetPageFlow(Type pageFlowType)
        {
            return GetPageFlow(pageFlowType, true);
        }

        private IPageFlow GetPageFlow(Type pageFlowType, bool createIfNotExists)
        {
            Guid instanceId = _store.GetByType(pageFlowType);

            if (instanceId != Guid.Empty)
            {
                return GetPageFlow(pageFlowType, instanceId);
            }
            else if (createIfNotExists)
            {
                return CreatePageFlow(pageFlowType);
            }

            return null;
        }

        private IPageFlow GetPageFlow(Type pageFlowType, Guid instanceId)
        {
            IPageFlow instance = _pageFlowFactory.GetPageFlow(pageFlowType, instanceId);
            RegisterPageFlowEventHandlers(instance);
            return instance;
        }

        public IPageFlow GetPageFlow(Guid instanceId)
        {
            string pageFlowTypeName = _store.GetInstanceType(instanceId);
            if (pageFlowTypeName == null)
                throw new PageFlowException(String.Format(CultureInfo.CurrentCulture, "The instance '{0}' was not found in the store.", instanceId));

            return GetPageFlow(Type.GetType(pageFlowTypeName), instanceId);
        }

        private IPageFlow CreatePageFlow(Type pageFlowType)
        {
            IPageFlow instance = _pageFlowFactory.GetPageFlow(pageFlowType);
            _store.Add(instance);
            RegisterPageFlowEventHandlers(instance);
            return instance;
        }

        private void RegisterPageFlowEventHandlers(IPageFlow pageFlow)
        {
            pageFlow.Aborted += new EventHandler<PageFlowAbortedEventArgs>(OnPageFlowAborted);
            pageFlow.Started += new EventHandler<PageFlowEventArgs>(OnPageFlowStarted);
            pageFlow.Suspended += new EventHandler<PageFlowEventArgs>(OnPageFlowSuspended);
            pageFlow.Completed += new EventHandler<PageFlowEventArgs>(OnPageFlowCompleted);
            pageFlow.Resumed += new EventHandler<PageFlowEventArgs>(OnPageFlowResumed);
        }

        private void OnPageFlowCompleted(object sender, PageFlowEventArgs e)
        {
            _store.Remove(e.PageFlow);
        }

        private void OnPageFlowSuspended(object sender, PageFlowEventArgs e)
        {
            _store.SetPageFlowNotRunning(e.PageFlow);
        }

        private void OnPageFlowResumed(object sender, PageFlowEventArgs e)
        {
            _store.SetPageFlowRunning(e.PageFlow);
        }

        private void OnPageFlowStarted(object sender, PageFlowEventArgs e)
        {
            _store.SetPageFlowRunning(e.PageFlow);
        }

        private void OnPageFlowAborted(object sender, PageFlowAbortedEventArgs e)
        {
            _store.Remove(e.AbortedPageFlowId);
        }

        public ProcessResult ProcessRequest(string url)
        {
            Guid instanceId = _store.GetLastRunningInstance();

            if (instanceId != Guid.Empty)
            {
                IPageFlow instance = GetPageFlow(instanceId);
                return ProcessWithRunningInstance(instance, url);
            }
            else
            {
                return ProcessWithNoRunningInstance(url);
            }
        }

        private ProcessResult ProcessWithNoRunningInstance(string url)
        {
            ProcessResult result;
            IPageFlowDefinition definition = PageFlowDirectory.Catalog.GetByUrl(url);
            if (definition != null)
            {
                IPageFlow instance = GetPageFlow(definition.PageFlowType, false);
                if (instance == null)
                {
                    result = ProcessWithNonExistingInstance(definition);
                }
                else
                {
                    result = ProcessWithExistingInstance(definition, instance);
                }
            }
            else
            {
                result = new ProcessResult(false, string.Empty); ;
            }

            return result;
        }

        private ProcessResult ProcessWithRunningInstance(IPageFlow instance, string url)
        {
            ProcessResult result = new ProcessResult(false, string.Empty);

            if (instance.CurrentPage.Url != url)
            {
                if (instance.Definition.ContainsUrl(url))
                {
                    Page requestedPage = instance.Definition.GetPageFromUrl(url);

                    if (instance.CanRedirectTo(requestedPage.Name))
                    {
                        instance.SetCurrentPage(requestedPage.Name);
                    }
                    else
                    {
                        result = new ProcessResult(true, instance.CurrentPage.Url);
                    }
                }
                else
                {
                    switch (instance.Definition.Abandonable)
                    {
                        case AbandonBehavior.AllowAndDiscardInstance:
                            instance.Abort(false);
                            result = ProcessRequest(url);
                            break;
                        case AbandonBehavior.AllowAndSaveInstance:
                            instance.Suspend();
                            result = ProcessRequest(url);
                            break;
                        case AbandonBehavior.Prevent:
                            result = new ProcessResult(true, instance.CurrentPage.Url);
                            break;
                    }
                }
            }
            return result;
        }

        private static ProcessResult ProcessWithExistingInstance(IPageFlowDefinition definition, IPageFlow instance)
        {
            ProcessResult result;
            switch (definition.Abandonable)
            {
                case AbandonBehavior.AllowAndSaveInstance:
                    result = ResumeInstance(instance, definition);
                    break;
                default:
                    result = new ProcessResult(true, definition.NotRunningRedirect);
                    break;
            }
            return result;
        }

        private static ProcessResult ProcessWithNonExistingInstance(IPageFlowDefinition definition)
        {
            ProcessResult result;
            if (HttpContext.Current != null && HttpContext.Current.Request.HttpMethod.Equals("POST", StringComparison.InvariantCultureIgnoreCase))
            {
                // NOTE: this special case happens when there is no running instance,
                //       there are no suspended instances, and the url belongs to a page flow
                //       but we are on PostBack. In this case we let the request flow, if not we
                //       send the user to NotRunning url.
                result = new ProcessResult(false, string.Empty);
            }
            else
            {
                result = new ProcessResult(true, definition.NotRunningRedirect);
            }
            return result;
        }

        private static ProcessResult ResumeInstance(IPageFlow instance, IPageFlowDefinition definition)
        {
            ProcessResult result;
            if (instance.Status == PageFlowStatus.Suspended)
            {
                instance.Resume();
                result = new ProcessResult(false, string.Empty);
            }
            else
            {
                result = new ProcessResult(true, definition.NotRunningRedirect);
            }
            return result;
        }    
    }
}
